﻿using System;
using System.IO;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

namespace Boggle {

    /// <summary>
    /// Main Window for Böggle.
    /// <BR>
    /// Author: David I. Schwartz (dis@mail.rit.edu)
    /// 
    /// Edited by: Robert Husfeldt(Just a bit!)
    /// <BR>
    /// </summary>
    public partial class BoggleWindow : Form {

        #region fields

        private string wordFileName;            // current word list filename
        private string diceFileName;            // current dice list filename
        private string saveFileName;            // current save filename
        private Board board;                    // buttons and locations on game board
        private Game currentGame;               // current Game (set by file loading window or Default button)
        private Button prevButton;              // previously selected button when testing a word
        private Stack<Button> buttonPath;       // Stack of currently selected Buttons (from 1st to most recent)
        private StringBuilder currentWord;      // word to test; formed from clicking Button
        private const int DELAY = 1000;         // pause before dialog windows
        private const string UNKNOWN = "?";     // default label for filenames
        public const int ROWS = 4;              // default row size of board
        public const int COLS = 4;              // default col size of board
        public const string DEFAULTWORDS =      // default dictionary file included in Debug folder
            "..\\..\\..\\words.txt";
        public const string DEFAULTDICE =       // default dice file included in Debug folder
            "..\\..\\..\\dice.txt";  

        #endregion fields

        #region properties

        /// <summary>
        /// Access dictionary.
        /// </summary>
        public string WordFileName {
            set { wordFileName = value; }
            get { return wordFileName; }
        }

        /// <summary>
        /// Access dice file.
        /// </summary>
        public string DiceFileName {
            set { diceFileName = value; }
            get { return diceFileName; }
        }
 
        /// <summary>
        /// Access the current game.
        /// </summary>
        public Game CurrentGame {
            set { currentGame = value; }
            get { return currentGame; }
        }

        /// <summary>
        /// GUI label for dictionary file.
        /// </summary>
        public Label DictionaryLabel { 
            get { return dictionaryLabel; } 
        }

        /// <summary>
        /// GUI label for dice file
        /// </summary>
        public Label DiceFileLabel { 
            get { return diceLabel; } 
        }
    
        /// <summary>
        /// Game board (composed of Buttons).
        /// </summary>
        public Board GameBoard { 
            get { return board; } 
        }

        /// <summary>
        /// Panel that holds game board.
        /// </summary>
        public Panel DicePanel { 
            get { return dicePanel; } 
        }

        #endregion properties

        #region construction and setup

        /// <summary>
        /// Set up main window.
        /// </summary>
        public BoggleWindow() {
            InitializeComponent();
            welcomePlayer();
            setUpGameParameters();
            setUpBoardButtons();
        } // constructor BoggleWindow

        /// <summary>
        /// Set up game parameters.
        /// </summary>
        private void setUpGameParameters() {
            currentWordLabel.Text = UNKNOWN;
            scoreLabel.Text = "0";
            wordCountLabel.Text = "0";
            solvedChronoBox.Text = "";
            solvedSortedBox.Text = "";
            wordFileName = null;
            diceFileName = null;
            dictionaryLabel.Text = UNKNOWN;
            diceLabel.Text = UNKNOWN;
            gameFileLabel.Text = UNKNOWN;
            dicePanel.Enabled = false;
            submitButton.Enabled = false;
            currentGame = null;
            prevButton = null;
            buttonPath = new Stack<Button>(0);
            currentWord = new StringBuilder("");
        } // method setUpGameParameters

        /// <summary>
        /// Set up buttons on Board and assign event handlers so player can click on each Die.
        /// </summary>
        private void setUpBoardButtons() {

            // Where is everything?
            GridLocation[,] grid = new GridLocation[ROWS, COLS];
            for (int row = 0; row < ROWS; row++)
                for (int col = 0; col < COLS; col++)
                    grid[row, col] = new GridLocation(row, col);

            // Buttons pre-created via design:
            Button[,] buttons = new Button[,] { 
                { button1,  button2,  button3,  button4  },
                { button5,  button6,  button7,  button8  },
                { button9,  button10, button11, button12 },
                { button13, button14, button15, button16 },
            };

            // set up the game board to "link" buttons and their locations:
            board = new Board(buttons, grid);

            // don't let player start yet:
            dicePanel.Enabled = false;
            submitButton.Enabled = false;

        } // method setButtons

        /// <summary>
        /// Reset board (keep current letter), GUI labels, and game parameters.
        /// </summary>
        private void resetBoard() {
            setUpGameParameters();
            board.resetAllLetters();
        } // method resetBoard

        /// <summary>
        /// Completely reset the game (and wipe out tile letters).
        /// </summary>
        private void resetGame() {
            setUpGameParameters();
            board.resetBlanks();
        }

        #endregion construction and setup

        #region general information

        /// <summary>
        /// Welcome the player.
        /// </summary>
        private void welcomePlayer() {
            DialogResult result = MessageBox.Show("Welcome to Böggle! Ready to play?", "Böggle", MessageBoxButtons.OKCancel);
            if (result == DialogResult.Cancel)
                quit(new FormClosingEventArgs(CloseReason.FormOwnerClosing, true));
        } // method welcomePlayer

        /// <summary>
        /// Explain game in Message Box.
        /// </summary>
        /// <param name="sender">Help button</param>
        /// <param name="e"></param>
        private void helpButton_Click(object sender, EventArgs e) {
            MessageBox.Show(
                            "Click either Default Game or Custom Game to begin playing." +
                            "\nTo save your results and current progress, click Save As...." +
                            "\nTo load a saved game, click Load." +
                            "\n\nOriginal instructions:" +
                            "\nhttp://www.hasbro.com/common/instruct/boggle.pdf."
                            );
        } // method helpButton_Click

        /// <summary>
        /// Show credits window.
        /// </summary>
        /// <param name="sender">Credits button</param>
        /// <param name="e"></param>
        private void Credits_Click(object sender, EventArgs e) {
            MessageBox.Show("Boggle(TM) by Hasbro.\nBöggle by David I. Schwartz");
        }

        #endregion general information

        #region start and quit

        /// <summary>
        /// Start a new game.
        /// </summary>
        /// <param name="sender">Start button</param>
        /// <param name="e"></param>
        private void startButton_Click(object sender, EventArgs e) {

            // if user already had a game running, maybe they want to save it:
            if (diceFileName != null && wordFileName != null) {
                DialogResult result =
                    MessageBox.Show("Save current game before starting new game?", "Alert!", MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes) 
                    saveGame();
            }

            // reset and then get new word/dice filenames:
            CustomGameWindow fileDialog = new CustomGameWindow(this);
            fileDialog.ShowDialog(new Form());

            // if user completed word&dice file entries, start new game:
            if (fileDialog.DialogResult == DialogResult.OK) {
                resetBoard();
                wordFileName = fileDialog.WordFileName;
                diceFileName = fileDialog.DiceFileName;
                dictionaryLabel.Text = Path.GetFileName(wordFileName);
                diceLabel.Text = Path.GetFileName(diceFileName);
                gameFileLabel.Text = UNKNOWN;

                // let user pick new words and dice:
                try {
                    currentGame = new Game(this);
                    dicePanel.Enabled = true;
                    submitButton.Enabled = true;
                }
                // uh oh! files weren't in expected format for Game object:
                catch (Exception) {
                    MessageBox.Show("Couldn't process one or more input files. Please try again.");
                    resetGame();
                }

            } // end if

        } // method startButton_Click

        /// <summary>
        /// Use built-in words and dice to test program.
        /// </summary>
        /// <param name="sender">Default button</param>
        /// <param name="e"></param>
        private void defaultButton_Click(object sender, EventArgs e) {

            // if user already had a game running, maybe they want to save it:
            if (diceFileName != null && wordFileName != null) {
                DialogResult result =
                    MessageBox.Show("Save current game before starting new game?", "Alert!", MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes) saveGame();
            }

            resetBoard();

            // go ahead and make the default game:
            wordFileName = DEFAULTWORDS;
            diceFileName = DEFAULTDICE;
            dictionaryLabel.Text = Path.GetFileName(wordFileName);
            DiceFileLabel.Text = Path.GetFileName(diceFileName);
            gameFileLabel.Text = UNKNOWN;

            try {
                currentGame = new Game(this);
                dicePanel.Enabled = true;
                submitButton.Enabled = true;
            }
            catch (Exception) {
                MessageBox.Show("Something wrong with starting new game. See if words and dice files are OK.");
                resetGame();
            }

        } // method defaultButton_Click

        /// <summary>
        /// Helper method to quit game.
        /// </summary>
        /// <param name="e">signal that we're trying to quit</param>
        public void quit(FormClosingEventArgs e) {

            // prevent forms from Closing if user chooses NOT to quit:
            e.Cancel = true;

            // if user already had a game running, maybe they want to save it:
            if (diceFileName != null && wordFileName != null) {
                DialogResult result =
                        MessageBox.Show("Do you want to save before quitting?", "Save?",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
                
                if (result == DialogResult.Yes)
                    saveGame();

                Dispose();
            }

            // otherwise, just ask if they want to quit:
            else {
                DialogResult result =
                        MessageBox.Show("Do you really want to quit?", "Quit?",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
                if (result != DialogResult.No)
                    Environment.Exit(1); // exiting entire game
            }

        } // method quit

        /// <summary>
        /// Quit the game.
        /// </summary>
        /// <param name="sender">Quit button</param>
        /// <param name="e"></param>
        private void quitButton_Click(object sender, EventArgs e) {
            quit(new FormClosingEventArgs(CloseReason.FormOwnerClosing, true));
        } 

        /// <summary>
        /// Another way to query player about not quitting :-) (pressing the "X" button on the main window title bar)
        /// </summary>
        /// <param name="sender">"X" button on titlebar of Böggle window</param>
        /// <param name="e">signal about closing</param>
        private void BoggleWindow_FormClosing(object sender, FormClosingEventArgs e) {
            quit(e);
        }

        #endregion start and quit

        #region save and load

        /// <summary>
        /// Save current game.
        /// </summary>
        /// <param name="sender">Save Button</param>
        /// <param name="e"></param>
        private void saveButton_Click(object sender, EventArgs e) {
            saveGame();
        }

        /// <summary>
        /// Helper method for save game (does all the work here).
        /// </summary>
        public void saveGame() {

            // need a current game:
            if (wordFileName == null || diceFileName == null) {
                MessageBox.Show("You can't save your game until you have at least loaded dictionary and dice files.");
            }

            // save current game state to user-input text file:
            else {

                // set up SaveFileDialog for text files:
                Stream myStream;
                SaveFileDialog saveFileDialog = new SaveFileDialog();

                saveFileDialog.Filter = "txt files (*.txt)|*.txt"; // just text
                saveFileDialog.FilterIndex = 1;                    // only one option           
                saveFileDialog.RestoreDirectory = false;           // let player keep opening same directory

                // prompt user:
                if (saveFileDialog.ShowDialog() == DialogResult.OK) {

                    // delay a "little while" before prompting for new file:
                    System.Threading.Thread.Sleep(DELAY);

                    // open stream and write data (if possible):
                    try {
                        // if file is OK, continue with saving:
                        if ((myStream = saveFileDialog.OpenFile()) != null) {

                            // create save file:
                            saveFileName = saveFileDialog.FileName;
                            StreamWriter writer = new StreamWriter(myStream);
                            writeSaveFile(writer);
                            writer.Close();

                        } // end writing stream

                        // alert user about new "Game File":
                        gameFileLabel.Text = Path.GetFileName(saveFileName);
                    }
                    catch (Exception) {
                        MessageBox.Show("Something went wrong in creating save file. Please try again.");
                    }

                } // end open file dialog

            } // end else (creating save file)

        } // method saveGame

        /// <summary>
        /// Helper method to write text to save file.
        /// </summary>
        /// <param name="writer">character stream for writing</param>
        private void writeSaveFile(StreamWriter writer) {

            // game parameters:
            writer.WriteLine(wordFileName);
            writer.WriteLine(diceFileName);
            writer.WriteLine(currentWordLabel.Text);
            writer.WriteLine(scoreLabel.Text);
            writer.WriteLine(wordCountLabel.Text);

            // dice and current selection state (most recent, previous, or none):
            for (int row = 0; row < ROWS; row++) {
                for (int col = 0; col < COLS; col++) {
                    writer.WriteLine(board[row, col].Text);
                    writer.WriteLine(board[row, col].BackColor.Name);
                }
            }

            // report which button player most recently pressed (could be none):
            if (prevButton != null)
                writer.WriteLine(prevButton.Name);
            else
                writer.WriteLine("null");

            // how many buttons in buttonPath?
            writer.WriteLine(buttonPath.Count);

            // need to report button path IN REVERSE (so stack re-builds in chronological ascent):
            Button[] buttons = buttonPath.ToArray();
            for (int i = buttons.Length - 1; i >= 0; i--)
                writer.WriteLine(buttons[i].Name);

            // previously solved words:
            writer.Write(solvedChronoBox.Text); // don't use WriteLine (adds extra space to file
            writer.Write(solvedSortedBox.Text); // which gets read as blank word!)

        } // method writeSaveFile

        /// <summary>
        /// Show Message Box to prompt to load previously saved game.
        /// </summary>
        /// <param name="sender">Load button</param>
        /// <param name="e"></param>
        private void loadButton_Click(object sender, EventArgs e) {

            // check if current game should end:
            if (diceFileName != null && wordFileName != null)
            {
                DialogResult result =
                    MessageBox.Show("Save current game before starting new game?", "Alert!", MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes) saveGame();
            }

            // delay a "little while" before prompting for new file:
            System.Threading.Thread.Sleep(DELAY);


            // prompt user to give a save file:
            OpenFileDialog dlg = new OpenFileDialog(); // prompt for file
            dlg.Filter = "txt files (*.txt)|*.txt";    // just text
            dlg.FilterIndex = 1;                       // only one option           
            dlg.RestoreDirectory = false;              // let player keep opening same directory

            // time to actually process that file:
            if (dlg.ShowDialog() == DialogResult.OK) {

                // clear current game/interface parameters:
                resetBoard();

                try {

                    // open save file and read in data:
                    using (TextReader reader = File.OpenText(dlg.FileName)) {

                        // load saved game:
                        Stack<string> chrono = new Stack<string>(0); // stack to store saved guesses
                        List<string> sorted = new List<string>(0);   // list to store solved words from before

                        // process save file and update word count based on words read in:
                        int solvedWordsCount = 0;
                        loadSaveFile(reader, chrono, sorted, out solvedWordsCount);

                        // start new game:
                        dicePanel.Enabled = true;
                        submitButton.Enabled = true;
                        currentGame = new Game(this, chrono, sorted, int.Parse(scoreLabel.Text), solvedWordsCount);

                    } // end using (file load)

                    // show player what they loaded:
                    gameFileLabel.Text = Path.GetFileName(dlg.FileName);

                }
                catch (Exception) {
                    MessageBox.Show("Something went wrong in trying to load file " + 
                        dlg.FileName + ".\nPlease try another file. Resetting game to fresh state.");
                    resetGame();

                }



            } // end file open dialog
            
        } // method loadButton_Click

        /// <summary>
        /// Parse data from saved game file.
        /// </summary>
        /// <param name="reader">saved file</param>
        /// <param name="chrono">stack of solved words (in order user had succeeded)</param>
        /// <param name="sorted">list of solved words (sorted alphabetically)</param>
        private void loadSaveFile(TextReader reader, Stack<string> chrono, List<string> sorted, out int solvedWordsCount) {
            // get and record file names:
            wordFileName = reader.ReadLine();
            diceFileName = reader.ReadLine();

            // was user testing a word in saved game?
            currentWordLabel.Text = reader.ReadLine();
            if (currentWordLabel.Text != UNKNOWN)
                currentWord = new StringBuilder(currentWordLabel.Text);
            else
                currentWord = new StringBuilder("");

            // update score/count and file labels:
            scoreLabel.Text = reader.ReadLine();
            wordCountLabel.Text = reader.ReadLine();
            dictionaryLabel.Text = Path.GetFileName(wordFileName);
            diceLabel.Text = Path.GetFileName(diceFileName);

            // get color/selection of each Button:
            for (int row = 0; row < ROWS; row++) {
                for (int col = 0; col < COLS; col++) {
                    Button tmp = board[row, col];
                    board[row, col].Text = reader.ReadLine();
                    Color c = Color.FromName(reader.ReadLine());

                    if (c.Equals(Board.defaultColor))
                        board.deSelect(tmp);
                    else if (c.Equals(Board.olderColor)) {
                        board.setOlder(tmp);
                        tmp.Enabled = false;
                    }
                    else if (c.Equals(Board.newestColor))
                        board.setNewest(tmp);
                }
            }

            // was there an active button from saved game?
            string tmpBtn = reader.ReadLine();
            if (tmpBtn == "null") prevButton = null;
            else prevButton = chooseButton(tmpBtn);

            // restore saved button selections:
            int pathCount = int.Parse(reader.ReadLine());
            for (int i = 0; i < pathCount; i++)
                buttonPath.Push(chooseButton(reader.ReadLine()));

            // update count of words solved so far:
            solvedWordsCount = int.Parse(wordCountLabel.Text);

            // get and load solution boxes:
            for (int i = 0; i < solvedWordsCount; i++) {
                string nextWord = reader.ReadLine();
                solvedChronoBox.Text += nextWord + Environment.NewLine;
                chrono.Push(nextWord);
            }

            for (int i = 0; i < solvedWordsCount; i++) {
                string nextWord = reader.ReadLine();
                solvedSortedBox.Text += nextWord + Environment.NewLine;
                sorted.Add(nextWord);
            }

        } // method loadSaveFile

        /// <summary>
        /// Find a Button corresponding to the supplied button name.
        /// </summary>
        /// <param name="name">supplied button name</param>
        /// <returns>associated button name or null if not found</returns>
        private Button chooseButton(string name) {
            foreach (Button b in board.Buttons)
                if (b.Name == name)
                    return b;
            return null;
        } // method chooseButton

        #endregion save and load

        #region solve

        /// <summary>
        /// Test player's selected word.
        /// </summary>
        /// <param name="sender">Submit button</param>
        /// <param name="e"></param>
        private void submitButton_Click(object sender, EventArgs e) {

            // get word:
            string currentWord = currentWordLabel.Text;

            // nothing entered yet, so quit:
            if (currentWord == UNKNOWN) {
                MessageBox.Show("You haven't selected any tile yet.");
                return;
            }

            // check if already solved:
            if (currentGame.checkSolution(currentWord)) {
                MessageBox.Show("You already solved \"" + currentWord + "\".");
                resetCurrentGuess();
            }

            // check if in dictionary:
            else if (!currentGame.checkDictionary(currentWord)){ 
                MessageBox.Show("\"" + currentWord + "\" isn't a word according to your dictionary.");
                resetCurrentGuess();
            }

            // otherwise, we have a winner! Add word to panels and store in current game:
            else {

                // scores:
                MessageBox.Show("Mazeltov!");
                currentGame.increaseScores(currentWord);
                currentGame.increaseSolvedCount();
                scoreLabel.Text = "" + currentGame.Score;
                wordCountLabel.Text = "" + currentGame.SolvedCount;

                // solved word boxes:
                currentGame.addWord(currentWord);
                solvedChronoBox.Text += currentWord + Environment.NewLine;
                solvedSortedBox.Lines = currentGame.getSortedWords();

                // reset game params:
                resetCurrentGuess();

            }

        } // method submitButton_Click

        /// <summary>
        /// User's word didn't work, so reset interface and current guess.
        /// </summary>
        private void resetCurrentGuess() {

            buttonPath = new Stack<Button>(0); // empty the letter stack
            prevButton = null;                 // no current button selected
            currentWord = new StringBuilder("");  // erase current word
            currentWordLabel.Text = UNKNOWN;          // revert label

            // reset to default color and deselected/enbaled status:
            foreach (Button b in board.Buttons) {
                board.deSelect(b); 
                b.Enabled = true;
            }

        } // method resetCurrentGuess

        #endregion solve

        #region board buttons

        /// <summary>
        /// Handle clicking of each Button on the game board.
        /// </summary>
        /// <param name="sender">Board button</param>
        /// <param name="e"></param>
        private void boardButton_Click(object sender, EventArgs e) {
            
            // which button was clicked?
            Button button = findButton((Button)sender);

            // is player starting a new word? (no Die currently selected):
            if (buttonPath.Count == 0)
                startWord(button);
            
            // is Die already selected? (user de-selecting previous tile):
            else if (button == prevButton)
                deselectLastDieButton(button);

            // try to select current Die:
            else
                selectNextDie(button);

        } // method boardButton_Click

        /// <summary>
        /// Try to select the next Die.
        /// </summary>
        /// <param name="button">Currently selected Button</param>
        private void selectNextDie(Button button) {

            // did we already select a previous Die? will we exceed the max-length word?  
            if (buttonPath.Count > 0 && buttonPath.Count <= board.Buttons.Length) {

                // user cannot deselect older buttons:
                prevButton.Enabled = false;          

                // if current button isn't already selected and is within range of last selected button,
                // then select current button and update potential word:
                if (board.withinRange(button, prevButton)) {
                    buttonPath.Push(button);                         // add button to path
                    board.setOlder(prevButton);                      // previous button shown in different color
                    board.setNewest(button);                         // current button highlighted
                    prevButton = button;                             // current button will be previous for next click
                    currentWord.Append(button.Text);                 // update potential word
                    currentWordLabel.Text = currentWord.ToString();  // show potential word to player
                }

                // button selected by user out-of-range:
                else {
                    prevButton.Enabled = true; // allow user to deselect most recent button
                }
            }

        } // method selectNextDie

        /// <summary>
        /// Deselect the most recently selected Button (if any).
        /// </summary>
        /// <param name="button">button to deselect</param>
        private void deselectLastDieButton(Button button) {

            // deselect and remove current button from button "path":
            board.deSelect(button);
            Button tmp = buttonPath.Pop();

            // remove letter(s) from end of potential word; need to account for Qu!
            if (tmp.Text != "Qu")
                currentWord.Remove(currentWord.Length - 1, 1); // remove last letter
            else
                currentWord.Remove(currentWord.Length - 2, 2); // remove last 2 letters
            currentWordLabel.Text = currentWord.ToString();

            // if deselected button was the last, nothing left in Stack:
            if (buttonPath.Count == 0)
                prevButton = null;

            // else, set and show previous Button:
            else {
                prevButton = buttonPath.Peek();
                prevButton.Enabled = true;
                board.setNewest(prevButton);
            }

        } // method deselectLastDieButton

        /// <summary>
        /// Start collecting letters for a word to submit if no Button currently selected.
        /// </summary>
        /// <param name="button">recently clicked board button</param>
        private void startWord(Button button) {
            buttonPath.Push(button);
            board.setNewest(button);
            prevButton = button;
            currentWord.Append(button.Text);
            currentWordLabel.Text = currentWord.ToString();
        } // method startWord

        /// <summary>
        /// Find specific Button associated with selected Button on board.
        /// </summary>
        /// <param name="b">Button selected by player</param>
        /// <returns>Button associated with player-selected Button</returns>
        private Button findButton(Button b) {
            foreach (Button other in board.Buttons)
                if (other.Equals(b))
                    return other;
            return null;
        }

        #endregion board buttons

        /// <summary>
        /// Calls both the solve method and creates a window upon player clicking the Get All Words button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Solver_Click(object sender, EventArgs e)
        {
            if (currentGame != null)
            {
                   AllWords showWords = new AllWords(currentGame.SortedSolution, currentGame.getAllWords(board.Buttons));
                   showWords.ShowDialog(new Form());

                   if (showWords.DialogResult == DialogResult.OK)
                   { }
            }
        }


    } // class BoggleWindow

} // Boggle
